package ar.uba.fi.tonyvaliente.documents;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import ar.uba.fi.tonyvaliente.files.File;
import ar.uba.fi.tonyvaliente.files.FileException;
import ar.uba.fi.tonyvaliente.files.RecordPosition;
import ar.uba.fi.tonyvaliente.persistors.RecordPositionPersistor;
import ar.uba.fi.tonyvaliente.persistors.StringPersistor;
import ar.uba.fi.tonyvaliente.tree.BPlusTree;
import ar.uba.fi.tonyvaliente.tree.BPlusTreeException;
import ar.uba.fi.tonyvaliente.tree.FileBPlusTree;
import ar.uba.fi.tonyvaliente.utils.Delta;
import ar.uba.fi.tonyvaliente.utils.RecursosAplicacion;

public class DocumentIndex extends Index {

	private static final String DOCS_ROOT = RecursosAplicacion.instance().getProperty("docs.root");
	
	private static final String defaultIndexPath = DOCS_ROOT + "/document_index.data";
	private static final String defaultListsPath = DOCS_ROOT + "/document_lists.data";
	private String indexPath;
	private String listsPath;
	BPlusTree<String, RecordPosition> indexPersistence;
	TermListFileWrapper termListFileWrapper;

	public DocumentIndex() {
		this(defaultIndexPath, defaultListsPath);
	}

	private DocumentIndex(String indexPath, String listsPath) {
		this.indexPath = indexPath;
		this.listsPath = listsPath;
	}

	@Override
	public void initialize(int keys) {
		ensureDelete();
		try {
			FileBPlusTree.create(keys, indexPath, true);
			File listPersistence = File.create(listsPath, 4096);
			termListFileWrapper = new TermListFileWrapper(listPersistence);
		} catch (BPlusTreeException e) {
			e.printStackTrace();
		} catch (FileException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void open() {
		if (indexPersistence != null)
			return;

		try {
			indexPersistence = FileBPlusTree.open(indexPath, new StringPersistor(), new RecordPositionPersistor());

			File listPersistence = File.open(listsPath, false);
			termListFileWrapper = new TermListFileWrapper(listPersistence);
		} catch (BPlusTreeException e) {
			e.printStackTrace();
		} catch (FileException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void close() {
		if (termListFileWrapper == null)
			return;

		try {
			termListFileWrapper.close();
			if (indexPersistence != null)
				indexPersistence.close();
		} catch (DocumentIndexException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BPlusTreeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public LinkedList<Integer> getDocuments(String term) {
		ensurePersistence();
		LinkedList<Integer> result = new LinkedList<Integer>();

		try {
			RecordPosition termListPosition = indexPersistence.get(term);
			if (termListPosition != null) {
				byte[] deltaTerms = termListFileWrapper
						.getTermList(termListPosition);
				List<Integer> tempResult = Delta.decodeToList(deltaTerms);
				result.addAll(tempResult);
			}
		} catch (NoSuchElementException e) {
			e.printStackTrace();
		} catch (BPlusTreeException e) {
			e.printStackTrace();
		} catch (DocumentIndexException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * Añade el término asociado al documento. Si el término ya estaba
	 * añadido añade el documento.
	 * 
	 * @param term
	 * @param documentId
	 */
	@Override
	public void add(Object word, int documentId) {
		String term = (String)word;
		ensurePersistence();

		RecordPosition termListPosition = null;
		byte[] deltaTermList = null;
		List<Integer> termList = null;

		// Primero obtenemos la posición de la lista.
		try {
			termListPosition = indexPersistence.get(term);
		} catch (NoSuchElementException e) {
			// No se hace nada, no es un error que la lista esté vacía.
			// de todos modos el BPlusTree no lanza esta excepción.
		} catch (BPlusTreeException e) {
			e.printStackTrace();
			// throw new DocumentIndexException("Error obteniendo la lista de
			// términos", e);
			// TODO Se lanza un RTE solo para no romper la interfaz.
			throw new RuntimeException(
					"Error obteniendo la lista de términos", e);
		}

		// luego obtenemos la lista
		if (termListPosition != null) {
			// desde el archivo si existe.
			try {
				deltaTermList = termListFileWrapper
						.getTermList(termListPosition);
			} catch (DocumentIndexException e) {
				e.printStackTrace();
				// TODO Se lanza un RTE solo para no romper la interfaz.
				throw new RuntimeException(
						"Error actualizando la lista de términos", e);
			}
			termList = Delta.decodeToList(deltaTermList);
		} else {
			// o la creamos
			termList = new ArrayList<Integer>();
		}

		// Añadimos el docId solo si no está
		if (!termList.contains(documentId)) {
			termList.add(documentId);
			// Nos aseguramos que se mantenga el orden
			Collections.sort(termList);
		}
		// y lo pasamos a delta
		deltaTermList = Delta.encode(termList);

		// actualizamos el archivo de la lista.
		RecordPosition updatePosition = new RecordPosition(1, 1);

		try {
			if (termListPosition != null) {
				// si existe.
				updatePosition = termListFileWrapper.updateTermList(
						deltaTermList, termListPosition);
			} else {
				// o la creamos
				updatePosition = termListFileWrapper
						.appendTermList(deltaTermList);
			}
		} catch (DocumentIndexException e) {
			e.printStackTrace();
			// TODO Se lanza un RTE solo para no romper la interfaz.
			throw new RuntimeException(
					"Error actualizando la lista de términos", e);
		}

		// Si corresponde actualizamos el árbol
		// si termListPosition es null siempre va a ser distinta de la
		// updatePosition
		if (!updatePosition.equals(termListPosition)) {
			try {
				indexPersistence.add(term, updatePosition);
			} catch (BPlusTreeException e) {
				e.printStackTrace();
				// throw new DocumentIndexException("Error actualizando la lista
				// de términos", e);
				// TODO Se lanza un RTE solo para no romper la interfaz.
				throw new RuntimeException(
						"Error actualizando la lista de términos", e);
			}
		}
	}

	//@Override
	public Iterator<String> getTermsIterator() {
		ensurePersistence();
		Iterator<String> it = indexPersistence.iterator();
		return it;
	}

	private void ensureDelete() {
		java.io.File file = new java.io.File(indexPath);
		if (file.exists()) {
			file.delete();
		}

		file = new java.io.File(listsPath);
		if (file.exists()) {
			file.delete();
		}
	}

	private void ensurePersistence() {
		if (indexPersistence != null)
			return;
		open();
	}
}
